<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2017/6/30
 * Time: 11:08
 */

namespace app\admin\controller;
use think\Controller;
use think\Cache;
use \SplQueue;
use think\Exception;

class AsyncBackupController extends Controller
{
    private $prefix                 =    ',';
    private $queue_store            =    '';
    private $tables_name            =    '';
    private $backupPath             =    '';
    private $backDs                 =   "\n";
    private $backDS                 =   '/';
    private $backEnd                =   ';';
    private $backPre_fix            =   '';
    private $backSql                =   '';
    private $backPhp_version        =   '';
    private $backMysql_version      =   '';
    private $backHost               =   '';
    private $backDatabase           =   '';
    private $backTable_files        =   '';
    private $backExtension          =   '.sql';
    private $backSystem             =   '';
    private $backServer_version     =   '';
    private $backFields             =   '';
    private $counter                =   0;
    private $connect                =   [
        // 数据库类型
        'type'            => 'mysql',
        // 服务器地址
        'hostname'        => '127.0.0.1',
        // 数据库名
        'database'        => 'jihui.cn',
        // 用户名
        'username'        => 'root',
        // 密码
        'password'        => '',
        // 端口
        'hostport'        => '',
        // 连接dsn
        'dsn'             => '',
        // 数据库连接参数
        'params'          => [],
        // 数据库编码默认采用utf8
        'charset'         => 'utf8',
        // 数据库表前缀
        'prefix'          => 'qhb_',
        // 数据集返回类型
        'resultset_type'  => 'array',
        // 是否需要进行SQL性能分析
        'sql_explain'     => false,
    ];
    private   function  _backInitDrive(){

        $this->_initQueue();$this->_backServerInfo();
        if(extension_loaded('redis')){
            if(Cache::has('tables')){
                $init_data          =   unserialize(Cache::get('tables'));
                $init_data          =   array_unique($init_data);
                foreach($init_data as $key=>$value){$this->queue_store->enqueue($value);}
            }
        }else{
            $this->_backFileData();
        }
        return $this;

    }



    public function  backupMultiple(){
        try{
            //允许客户端关闭 让程序驻留
            ignore_user_abort(true);
            //设置程序执行时间为不超时
            set_time_limit(0);
            //初始化当前的驱动以及消息队列存储数据
            $this->_backInitDrive()->_backupData();
        }catch (Exception $exception){
            file_put_contents( 'error.text',$exception->getMessage()."code :".$exception->getCode()."LINe : ".$exception->getLine());
        }
    }


    /**
     *          备份数据    多表备份   全部备份  有待解决的问题的就是mysql go way
     */
    private function  _backupData(){
        $this->_backInitSeverInfo();
        while(!$this->queue_store->isEmpty()){
            $this->tables_name               =     $this->queue_store->dequeue();
            $backResult                      =     $this->_backCheckTables();
            if(false===$backResult){continue;}
            $this->_backSqlInFile();
        }
    }


    private function _backSqlInFile(){
        $this->_backHeaderInfo();
    }


    private function _backHeaderInfo(){
        $this->backSql.=        "---------------------------------备份数据表:{$this->tables_name}-------------------------------".$this->backDs;
        $this->backSql.=        "-----备份数据库: {$this->backDatabase}".$this->backDs;
        $this->backSql.=        "-----备份主机: {$this->backHost}".$this->backDs;
        $this->backSql.=        "-----备份人: lqc".$this->backDs;
        $this->backSql.=        "-----备份联系电话: 18824267187".$this->backDs;
        $this->backSql.=        "-----备份服务器: {$this->backServer_version}".$this->backDs;
        $this->backSql.=        "-----备份服操作系统: {$this->backSystem}".$this->backDs;
        $this->backSql.=        "-----备份PHP版本: {$this->backPhp_version}".$this->backDs;
        $this->backSql.=        "-----备份mysql版本: {$this->backMysql_version}".$this->backDs;
        $this->backSql.=        "-----备份日期: ".date('Y 年 m 月 d 日 H时 i分 s秒').$this->backDs;
        $this->backSql.=        "------------------------------------------------------------------------------------------------".$this->backDs.$this->backDs.$this->backDs.$this->backDs;
        $this->backSql.=        "DROP TABLE IF EXISTS {$this->backPre_fix}{$this->tables_name}".$this->backEnd.$this->backDs.$this->backDs.$this->backDs;
        $construct     =        $this->_backTableConstruct();
       $this->backSql.=$construct.$this->backEnd.$this->backDs.$this->backDs.$this->backDs;
        $this->_backLanInsert();
    }

    /**
     * 备份insert sql语句
     */
    private function _backLanInsert(){
        $this->_backField();
        //chunk 可以做可能在其他的方面chunk的性能可能还会更加的优化。。但是这里考虑到存储分卷的大小就不在使用chunk因为重置
        /*$backDataFlush    =   [];
        //使用chunk的原因是我们需要减轻数据库的查询压力所以chunk是进行分页查询
        $backData           =   db()->connect($this->connect)->table("{$this->backPre_fix}{$this->tables_name}")->chunk(1000,function($chunkData)use(&$backDataFlush){
            $backDataFlush  =   array_merge($chunkData,$backDataFlush);
        });*/
        $resultData         =   db()->connect($this->connect)->table($this->backPre_fix.$this->tables_name)->select();
        $length             =   (1024*1024*2);
        $fileNumber         =   1;
        $this->backupPath   =   $this->backupPath.$this->backDS.$this->tables_name.$this->backDS.date('Y-m-d').$this->backDS;
        $this->_makeDir($this->backupPath);
        do{
            $this->backSql.=$this->_backBuildInsert($resultData[$this->counter]);
            if(strlen($this->backSql)>=$length){
                $this->_backWriteFile($fileNumber);
                ++$fileNumber;
                $this->backSql= '';
            }
            unset($resultData[$this->counter]);
            ++$this->counter;
        }while(sizeof($resultData)>0);
        //储存余下的sql语句
        $this->_backWriteFile($fileNumber);
        //重置索引以及储存机制
        $this->backSql        = '';$this->counter=0;$this->backupPath=config('backupFile');
    }

    private function _backBuildInsert($data){
        $insertLan          =   "INSERT  INTO  '{$this->backPre_fix}{$this->tables_name}' ( {$this->backFields} )  VALUE  ( ";
        $data               =array_map(function(&$element){
            if(!is_numeric($element)){
               $element    ="'".$element."'";
            }
          return   $element       =empty($element)?'':$element;
        },$data);
        $values             =   implode(',',$data);
        $insertLan   =   $insertLan.$values.' )'.$this->backDs;
        return addslashes($insertLan);          // stripcslashes() 导入需要反转意
    }


    private function _backField(){
        $this->backFields   =   db()->connect($this->connect)->table($this->backPre_fix.$this->tables_name)->getTableInfo($this->backPre_fix.$this->tables_name);
        $this->backFields   =   $this->backFields['fields'];
        $this->backFields   =   array_map(function($element){
            //处理是为了防止关键字的出现
            if(empty($element)){return "";}
            return "'".$element."'";
        },$this->backFields);
       return  $this->backFields   =   implode(',',$this->backFields);
    }

    /**
     * @return mixed 获得表结构
     */
    private function _backTableConstruct(){
       $tableConstruct =  db()->connect($this->connect)->query("SHOW  CREATE TABLE {$this->backPre_fix}{$this->tables_name}");
       return $tableConstruct[0]['Create Table'];
    }



    private function _backInitSeverInfo(){
        $this->backPhp_version              =       PHP_VERSION;
        $this->backMysql_version            =       db()->connect($this->connect)->query('SELECT VERSION()  as version');
        list($this->backMysql_version)      =       $this->backMysql_version;
        $this->backMysql_version            =       $this->backMysql_version['version'];
        $this->backSystem                   =       PHP_OS;
        $this->backServer_version           =       $_SERVER['SERVER_SOFTWARE'];
    }



    private function _backCheckTables(){
        $backResult                         =       db()->connect($this->connect)->query("SHOW TABLES LIKE '{$this->backPre_fix}{$this->tables_name}'");
        if(empty($backResult)){return false;};
        return true;
    }


    public function _backServerInfo(){
        $this->backupPath           =   config('backupFile');
        $this->backPre_fix          =   config('database.prefix');
        $this->backDatabase         =   config('database.database');
        $this->backHost             =   config('database.hostname');
        if(!empty($this->connect)){
            $this->backPre_fix      =   $this->connect['prefix'];
            $this->backDatabase     =   $this->connect['database'];
            $this->backHost         =   $this->connect['hostname'];
        }
    }





    private function _initQueue(){
        $this->queue_store          =   new SplQueue();
    }

    private function _makeDir($dir){
        // linux 可是使用posix系统标准来达到设置权限的目的。。前提是该执行程序应该具有比较高的权限
        umask(0);
        return  is_dir ($dir ) or $this->_makeDir(dirname( $dir )) and  mkdir ( $dir , 0755);
    }

    private function _backWriteFile($subsectionNumber=1){
        try{
            // 文件名称是 /backData/TableName/date/table_name + val + subsectionNumber.sql
            $filesName   =$this->backupPath.$this->tables_name.chr(95).'val'.chr(95).$subsectionNumber.$this->backExtension;
            $fp          =fopen($filesName,'w+');
            if(!is_resource($fp)){ throw  new Exception('打开文件失败！');}
            flock($fp,LOCK_EX);
            fwrite($fp,$this->backSql,strlen($this->backSql));
            flock($fp,LOCK_UN);
            fclose($fp);
            return true;
        }catch (Exception $exception){return false;}
    }



    private  function _backFileData(){
        $path                       =   config('backupFile');
        if(!file_exists($path)){return false;}
        $fp                         =   fopen($path,'r+');
        flock($fp,LOCK_EX);
        while(!feof($fp)){
           $value                   =   fgets($fp);
           $value                   =   str_replace("\r\n",'',$value);
          $this->queue_store->enqueue($value);
        }
        flock($fp,LOCK_UN);
        fclose($fp);
    }
}